use actix::prelude::*;
use chrono::Local;
use chrono::prelude::*;
use md5::Digest;
use md5::Md5;
use std::fs;
use crate::LOCAL_CONFIG;
use crate::action::allow_list_action;
use crate::action::setting_action;
use crate::config::ConfigAction;
use crate::ip_block;
use crate::kv::KVStore;
use crate::{kv, DbPool, actions, models};

use tokio::io::Interest;
use tokio::net::UnixDatagram;
use std::io;
use std::{net::IpAddr, str::FromStr};
use maxminddb::geoip2;
use std::collections::HashMap;
use crate::model;


// Define actor
pub struct SuricataScheduler {
    db: kv::RocksDB,
    pool: DbPool,
    ip_block: ip_block::Iptables,
}

// Provide Actor implementation for our actor
impl Actor for SuricataScheduler {
    type Context = Context<Self>;

    fn started(&mut self, _ctx: &mut Self::Context) {
        println!("SuricataScheduler Actor is started");
        let arbiter = Arbiter::new();
        arbiter.spawn(SuricataScheduler::execute(self.db.clone(), self.pool.clone(), self.ip_block.clone()));
        //ctx.spawn(SuricataScheduler::execute(self.search.clone()));
    }

    fn stopped(&mut self, _ctx: &mut Context<Self>) {
        println!("SuricataScheduler Actor is stopped");
    }
}

impl SuricataScheduler {

    pub(crate) fn get_scheduler(db: kv::RocksDB, pool: DbPool, ip_block: ip_block::Iptables) -> Self {
        SuricataScheduler { db, pool, ip_block }
    }

    async fn execute(db: kv::RocksDB, pool: DbPool, ip_block: ip_block::Iptables) {

        // init maxminddb
        let reader = maxminddb::Reader::open_readfile("data/GeoLite2-City.mmdb").unwrap();

        

        let conn = pool.get().unwrap();

        let client_path = "/var/run/suricata.sock";
        let _ = fs::remove_file(client_path);
        let socket = UnixDatagram::bind(&client_path).unwrap();
        let mut data = [0; 1024 * 1024];
        loop {
            let ready = socket.ready(Interest::READABLE).await.unwrap();
            if ready.is_readable() {
                match socket.try_recv(&mut data[..]) {
                    Ok(n) => {
                        
                        let key = Local::now().format("%Y%m%d%H%M%S%f").to_string();
                        
                        let doc_json = String::from_utf8_lossy(&data[..n]).to_string();
                        let value: serde_json::Value = serde_json::from_str(doc_json.as_str()).unwrap();
                        if value["event_type"].as_str().unwrap() == "alert" {
                            
                            let rule_id = value["alert"]["signature_id"].as_u64().unwrap();
                            println!("SuricataScheduler socket.try_recv {} {}", key, rule_id);

                            let timestamp = value["timestamp"].as_str().unwrap().parse::<DateTime<Utc>>().unwrap().timestamp_millis();
                            
                            // save to rocksdb
                            db.save(&key, &doc_json);

                            let lang = match LOCAL_CONFIG.get("lang") {
                                Some(v) => v.to_string(),
                                None => "zh-CN".to_string(),
                            };
                            let rule_key = &format!("suricata-{}", rule_id);

                            
                            let mut rule_value = String::from("");
                            while rule_value == "" {
                                match db.find(rule_key) {
                                    Some(r) => {
                                        rule_value = r;
                                        break;
                                    },
                                    None => {
                                        std::thread::sleep(std::time::Duration::from_millis(1000));
                                    },
                                }
                            }

                            // let rule: Rule = serde_json::from_str(&rule_value).unwrap();
                            let rule_with_lang: HashMap<String, model::rule_meta_model::RuleMeta> = serde_json::from_str(&rule_value).unwrap();
                            let rule = rule_with_lang.get(&lang).unwrap();

                            let mal_obj_key = value[rule.mal_obj_field.clone()].as_str().unwrap();
                            //let mal_obj_key = "132.43.23.23";

                            // 
                            // let mal_obj = json!([{
                            //     "mal_obj_type": rule.mal_obj_type,
                            //     "mal_obj_key": mal_obj_key,
                            // }]);
                            // let mal_obj_json = serde_json::to_string(&mal_obj).unwrap();

                            //if mal_obj_key.starts_with("192.168.") || mal_obj_key.starts_with("10.") ||  mal_obj_key.starts_with("172.16.") || 

                            let ip: IpAddr = FromStr::from_str(mal_obj_key).unwrap();
                            let city_result = reader.lookup(ip);
                            let (country, city) = if city_result.is_ok() {
                                let city: geoip2::City = city_result.unwrap();
                                let country = match city.country {
                                    Some(c) => match c.names {
                                        Some(n) => match n.get(lang.as_str()) {
                                            Some(lang) => lang.to_string(),
                                            None => "".to_string(),
                                        },
                                        None => "".to_string(),
                                    },
                                    None => "".to_string(),
                                };
                            
                                let city = match city.city {
                                    Some(c) => match c.names {
                                        Some(n) => match n.get(lang.as_str()) {
                                            Some(lang) => lang.to_string(),
                                            None => "".to_string(),
                                        },
                                        None => "".to_string(),
                                    },
                                    None => "".to_string(),
                                };
    
                                (country, city)
                            } else {
                                ("".to_string(), "".to_string())
                            };

                            
                            let mut new_alert = models::Alert {
                                id: key.clone(),
                                severity: rule.severity.clone(),
                                category: rule.category.clone(),
                                description: rule.name.clone(),
                                time: timestamp,
                                source: "网络流量".to_string(),
                                mal_obj_type: rule.mal_obj_type.clone(),
                                mal_obj_key: mal_obj_key.to_string(),
                                mal_obj_country: country.clone(),
                                mal_obj_city: city,
                                rule_id: rule_id.to_string(),
                                solved: false,
                            };

                            // 自动处置 TODO 自动处置开关判断
                            if country != "" {
                                let setting_result = setting_action::find_by_id("response-ip-block", &conn);
                                if setting_result.is_ok() {
                                    let setting_op = setting_result.unwrap();
                                    match setting_op {
                                        Some(setting) => {
                                            if setting.value == "true" {
                                                let mut hasher = Md5::new();
                                                hasher.update("IP");
                                                hasher.update(mal_obj_key.to_string().as_bytes());
                                                let result = hasher.finalize();
                                                let allow_list_id = format!("{:x}", result);
                                                let allow_list_result = allow_list_action::find_by_id(&allow_list_id, &conn);
                                                if allow_list_result.is_err() || allow_list_result.unwrap().is_none() {
                                                    let solve_result = actions::solve_alert_op(&new_alert, "自动", &ip_block, &conn);
                                                    println!("{:?}", solve_result);
                                                }
                                                new_alert.solved = true;
                                            }
                                        },
                                        None => (),
                                    }
                                }
                            }
                            
                            // save to sqlite
                            let _insert_new_alert_result = actions::insert_new_alert(new_alert, &conn);

                            // println!("{:?}", insert_new_alert_result);
                        }
                        
                    }
                    // False-positive, continue
                    Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
                    Err(e) => {
                        println!("error {:?}", e);
                        break;
                    }
                }
            }
        }
    }
}